#ifndef _VEC_H_
#define _VEC_H_

#include <cmath>
#include <algorithm>
#include "mat.hpp"

enum Axis
{
	NOAXIS = -1,
	X,
	Y,
	AXISCT
};

template <typename T>
struct Vec2
{
	T v[AXISCT];

	T & x;
	T & y;

	Vec2(T x0 = 0, T y0 = 0)
		: x(v[X]), y(v[Y])
	{
		v[X] = x0;
		v[Y] = y0;
	}

	Vec2(const Vec2 & vec)
		: x(v[X]), y(v[Y])
	{
		v[X] = vec[X];
		v[Y] = vec[Y];
	}

	T & operator[](unsigned ax)
	{
		return v[ax % AXISCT];
	}
	
	const T & operator[](unsigned ax) const
	{
		return v[ax % AXISCT];
	}

	template <class T1>
	operator Vec2<T1>() const
	{
		return Vec2<T1>((T1)v[X], (T1)v[Y]);
	}

	bool operator==(const Vec2 & vec) const
	{
		return v[X] == vec[X] && v[Y] == vec[Y];
	}

	bool operator!=(const Vec2 & vec) const
	{
		return !(*this == vec);
	}
	
	bool operator<(const Vec2 & vec) const
	{
		return LenSq() < vec.LenSq();
	}

	bool operator>(const Vec2 & vec) const
	{
		return LenSq() > vec.LenSq();
	}

	bool operator<=(const Vec2 & vec) const
	{
		return !(*this > vec);
	}

	bool operator>=(const Vec2 & vec) const
	{
		return !(*this < vec);
	}

	Vec2 & operator=(const Vec2 & vec)
	{
		v[X] = vec[X];
		v[Y] = vec[Y];
		return *this;
	}

	Vec2 & operator+=(const Vec2 & vec)
	{
		v[X] += vec[X];
		v[Y] += vec[Y];
		return *this;
	}

	Vec2 operator+(const Vec2 & vec) const
	{
		Vec2 r(*this);
		r += vec;
		return r;
	}

	Vec2 & operator-=(const Vec2 & vec)
	{
		v[X] -= vec[X];
		v[Y] -= vec[Y];
		return *this;
	}

	Vec2 operator-() const
	{
		return *this * -1;
	}

	Vec2 operator-(const Vec2 & vec) const
	{
		Vec2 r(*this);
		r -= vec;
		return r;
	}

	Vec2 & operator*=(T s)
	{
		v[X] *= s;
		v[Y] *= s;
		return *this;
	}

	Vec2 operator*(T s) const
	{
		Vec2 vec(*this);
		vec *= s;
		return vec;
	}

	Vec2 & operator/=(T s)
	{
		v[X] /= s;
		v[Y] /= s;
		return *this;
	}

	Vec2 operator/(T s) const
	{
		Vec2 vec(*this);
		vec /= s;
		return vec;
	}

	Vec2 LowerBound(const Vec2 & vec) const
	{
		return Vec2(std::max(v[X], vec[X]), std::max(v[Y], vec[Y]));
	}

	Vec2 UpperBound(const Vec2 & vec) const
	{
		return Vec2(std::min(v[X], vec[X]), std::min(v[Y], vec[Y]));
	}

	T Dot(const Vec2 & vec) const
	{
		return (v[X] * vec[X]) + (v[Y] * vec[Y]);
	}

	T LenSq() const
	{
		return Dot(*this);
	}

	T DistSq(const Vec2 & vec) const
	{
		return (vec - *this).LenSq();
	}

	Vec2 & Norm()
	{
		return (*this /= std::sqrt(LenSq()));
	}

	Vec2 operator*(const Mat<T> &mat) const
	{
		Vec2 vec (*this);
		vec *= mat;
		return vec;
	}
	
	Vec2& operator*=(const Mat<T> &mat)
	{
		x = mat.TransformX(x, y);
		y = mat.TransformY(x, y);
		return *this;
	}
};

typedef Vec2<int> Vec2i;
typedef Vec2<float> Vec2f;

template <class T>
struct Box2
{
	Vec2<T> pos;
	Vec2<T> size;

	Box2()
		: pos(0, 0), size(0, 0)
	{
	}

	Box2(const Vec2<T> & pos0, const Vec2<T> & size0)
		: pos(pos0), size(size0)
	{
	}

	Box2(const Box2 & b)
		: pos(b.pos), size(b.size)
	{
	}

	template <class T1>
	operator Box2<T1>()
	{
		return Box2<T1>(pos, size);
	}

	T AxHalf(unsigned ax) const
	{
		return size[ax] * .5f;
	}

	Vec2<T> HSize() const
	{
		return Vec2<T>(AxHalf(X), AxHalf(Y));
	}

	T AxPoint(unsigned ax, float side) const
	{
		return pos[ax] + (side * size[ax]);
	}

	Vec2<T> Point(float sidex, float sidey) const
	{
		return Vec2<T> (AxPoint(X, sidex), AxPoint(Y, sidey));
	}

	T AxMin(unsigned ax) const
	{
		return std::min(AxPoint(ax, 0), AxPoint(ax, 1));
	}

	T AxCenter(unsigned ax) const
	{
		return AxPoint(ax, .5f);
	}
	
	T AxMax(unsigned ax) const
	{
		return std::max(AxPoint(ax, 0), AxPoint(ax, 1));
	}

	T AxHit(const Box2 & b, unsigned ax) const
	{
		if (AxCenter(ax) < b.AxCenter(ax))
			return AxMax(ax) - b.AxMin(ax);
		return AxMin(ax) - b.AxMax(ax);
	}

	Vec2<T> HitVec(const Box2 & b) const
	{
		if (!Hitting(b))
			return Vec2<T>(0, 0);
		return Vec2<T>(AxHit(b, X), AxHit(b, Y));
	}

	bool Hitting(const Box2 & b) const
	{
		if (AxMax(X) < b.AxMin(X))
			return false;
		if (b.AxMax(X) < AxMin(X))
			return false;
		if (AxMax(Y) < b.AxMin(Y))
			return false;
		if (b.AxMax(Y) < AxMin(Y))
			return false;
		return true;
	}

	bool Hitting(const Vec2<T> & pt) const
	{
		return Hitting(Box2<T>(pt, Vec2<T>(0, 0)));
	}

	float Ratio() const
	{
		return (float)size[X] / size[Y];
	}

	float InvRatio() const
	{
		return (float)size[Y] / size[X];
	}

	Vec2<T> LeftTop() const
	{
		return Vec2<T>(AxMin(X), AxMin(Y));
	}
	
	Vec2<T> CentTop() const
	{
		return Vec2<T>(AxCenter(X), AxMin(Y));
	}
	
	Vec2<T> RightTop() const
	{
		return Vec2<T>(AxMax(X), AxMin(Y));
	}
	
	Vec2<T> LeftCent() const
	{
		return Vec2<T>(AxMin(X), AxCenter(Y));
	}
	
	Vec2<T> Center() const
	{
		return Vec2<T>(AxCenter(X), AxCenter(Y));
	}
	
	Vec2<T> RightCent() const
	{
		return Vec2<T>(AxMax(X), AxCenter(Y));
	}
	
	Vec2<T> LeftBot() const
	{
		return Vec2<T>(AxMin(X), AxMax(Y));
	}

	Vec2<T> CentBot() const
	{
		return Vec2<T>(AxCenter(X), AxMax(Y));
	}

	Vec2<T> RightBot() const
	{
		return Vec2<T>(AxMax(X), AxMax(Y));
	}

	Box2 operator+(const Vec2<T> & offset) const
	{
		return Box2(pos + offset, size);
	}

	Box2 operator-(const Vec2<T> & offset) const
	{
		return Box2(pos - offset, size);
	}
};

typedef Box2<int> Box2i;
typedef Box2<float> Box2f;

template <class T> struct AxPlane
{
	int axis;
	T dist;

	AxPlane<T>(int ax, T d = 0)
	{
		axis = ax;
		dist = d;
	}

	T PointDist(Vec2<T> & p)
	{
		return p[axis] + dist;
	}
};

typedef AxPlane<float> AxPlanef;

template <class T> struct Plane
{
	Vec2<T> norm;
	T dist;
	
	Plane<T>()
	{
		norm[X] = norm[Y] = 0;
		dist = 0;
	}

	Plane<T>(Vec2<T> & n, T d = 0)
	{
		NormSet(n);
		dist = d;
	}

	Plane<T>(Vec2<T> & n, Vec2f & p)
	{
		NormSet(n);
		DistSet(p);
	}

	Vec2<T> & NormSet(Vec2<T> & n)
	{
		return norm = n.Norm();
	}

	T DistSet(Vec2<T> & p)
	{
		return dist = std::sqrt(p.LenSq());
	}

	T PointDist(Vec2<T> & p)
	{
		return norm.Dot(p) + dist;
	}
};

typedef Plane<float> Planef;

#endif
